// Addin.h : Declaration of the CAddin

#pragma once
#include "resource.h"       // main symbols

#include "AddinLib_i.h"
#include "PowerPointSink.h"
#include "HTMLWindow.h"

#include <atlcoll.h>

using namespace ATL;

// CAddin

typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1> 
	IDTImpl;

typedef IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5>
	RibbonImpl;

typedef IDispatchImpl<ICustomTaskPaneConsumer, &__uuidof(ICustomTaskPaneConsumer), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5>
	CustomTaskPaneConsumerImpl;

typedef IDispatchImpl<IAddin, &IID_IAddin, &LIBID_StatCloud9MsoAddin, /*wMajor =*/ 1, /*wMinor =*/ 0> 
	AddinImpl;

class ATL_NO_VTABLE CAddin :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CAddin, &CLSID_StatCloudAddin>,
	public AddinImpl,
	public ISupportErrorInfo,
	public IDTImpl,
	public RibbonImpl,
	public CustomTaskPaneConsumerImpl
{
private:
	CComPtr<PowerPoint::_Application> pApplication;

	CComPtr<IRibbonUI> pRibbonUI;
	CComDispatchDriver pRibbonHandler;

	CComPtr<_CustomTaskPane> pTaskPane;

	CSimpleMap<DISPID, CComBSTR> ribbonCallbackNames;

	CInterfaceArray<IHtmlWindow> windows;
	CInterfaceArray<IDispatch> appEventsListeners;

public:
	CAddin()
	{
	}

	DECLARE_REGISTRY_RESOURCEID(IDR_ADDIN)

	BEGIN_COM_MAP(CAddin)
		COM_INTERFACE_ENTRY2(IDispatch, IAddin)
		COM_INTERFACE_ENTRY(IAddin)
		COM_INTERFACE_ENTRY(_IDTExtensibility2)
		COM_INTERFACE_ENTRY(IRibbonExtensibility)
		COM_INTERFACE_ENTRY(ICustomTaskPaneConsumer)
	END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

public:

	// IDispatch override
	// Annoyingly, the RDispatch 
	STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
		LCID lcid, DISPID* rgdispid)
	{
		// First check to see if this method name is one defined on our Addin's interface (IAddin)
		HRESULT hr = AddinImpl::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);

		if(FAILED(hr) && pRibbonHandler && cNames == 1) {  
			// we don't want to forward the js internal function "valueOf"-- this 
			// should fail because we are not a JS object
			if(CComBSTR("valueOf") == rgszNames[0])
				return hr;
			
			// all other calls are probably callbacks from the Ribbon interface
			// check first to see if its defined on our callbackHandler
			DISPID jsDispid;
			hr = pRibbonHandler->GetIDsOfNames(IID_NULL, rgszNames, cNames, lcid, &jsDispid);
			if(FAILED(hr)) 
				return hr;

			// we need to make up an ID number for the method name, and then store the name so that 
			// when invoke is called by DISID, we can invoke the method on the JavaScript object by name
			// note that we don't cache the dispid as the handler may be changed between calls
			*rgdispid = ribbonCallbackNames.ReverseLookup(rgszNames[0]);
			if(*rgdispid == 0) {
				DISPID newId = 9000 + ribbonCallbackNames.GetSize();
				ribbonCallbackNames.Add(newId, rgszNames[0]);
				*rgdispid = newId;
			}
		}
		return hr;
	}

	STDMETHOD(Invoke)(DISPID dispidMember, 
		const IID &riid, 
		LCID lcid, 
		WORD wFlags, 
		DISPPARAMS *pdispparams, 
		VARIANT *pvarResult, 
		EXCEPINFO *pexceptinfo, 
		UINT *puArgErr)
	{
		CComBSTR callbackName = ribbonCallbackNames.Lookup(dispidMember);
		if(callbackName) {
			return ForwardRibbonCallback(callbackName, riid, lcid, wFlags, pdispparams, pvarResult, pexceptinfo, puArgErr);

		} else { 
			return AddinImpl::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, 
					pvarResult, pexceptinfo, puArgErr);
		}
	}

	HRESULT ForwardRibbonCallback(
		BSTR callbackName,
		const IID &riid, 
		LCID lcid, 
		WORD wFlags, 
		DISPPARAMS *pdispparams, 
		VARIANT *pvarResult, 
		EXCEPINFO *pexceptinfo, 
		UINT *puArgErr) 
	{
		HRESULT hr; 

		if(!pRibbonHandler)
			return E_FAIL;
	
		DISPID jsMemberId;
		hr = pRibbonHandler->GetIDsOfNames(IID_NULL, &callbackName, 1, 0, &jsMemberId);
		if(FAILED(hr))
			return hr;

		int cArgs = pdispparams->cArgs;

		// make sure this maches the signature of a ribbon callback (1 or 2 params)
		if(pdispparams->cArgs == 0 || cArgs > 3 || pdispparams->cNamedArgs > 0)
			return E_FAIL;

		// Arguments passed in reverse order!!
		if(pdispparams->rgvarg[cArgs - 1].vt != VT_DISPATCH)
			return E_FAIL;

		// I think someone screwed up the IRibbonControl IDL, and the result is that 
		// "Id" property of IRibbonControl has a plain [out] parameter rather than
		// an automation-friendly [retval] parameter, making it impossible to use from JScript. 
		//
		// We could wrap it in our interface, but really all we need is the id, so we'll just 
		// pass that instead of the interface
		CComPtr<IRibbonControl> pCtrl;
		hr = pdispparams->rgvarg[cArgs - 1].pdispVal->QueryInterface(&pCtrl);
		if(FAILED(hr))
			return hr;

		CComBSTR controlId;
		hr = pCtrl->get_Id(&controlId);
		if(FAILED(hr))
			return hr;

		CComVariant varControlId(controlId);

		if(cArgs == 1) {
			hr = pRibbonHandler.Invoke1(jsMemberId, &varControlId, pvarResult); 
		} else {
			hr = pRibbonHandler.Invoke2(jsMemberId, &varControlId, &(pdispparams->rgvarg[cArgs - 2]), pvarResult);
		} 
		return hr;
	}

	// _IDTExtensibility2 Methods
public:
	STDMETHOD(OnConnection)(LPDISPATCH pApplication, ext_ConnectMode ConnectMode, LPDISPATCH pAddinDisp, 
		SAFEARRAY * * custom)
	{
		HRESULT hr = pApplication->QueryInterface(&(this->pApplication));
		if(FAILED(hr))
			return hr;
		
		// Store a reference to ourselves in the Addin.Object so that 
		// VBA can access us
		CComPtr<COMAddIn> pAddin;
		hr = pAddinDisp->QueryInterface(&pAddin);
		if(SUCCEEDED(hr) ) 
			hr = pAddin->put_Object(static_cast<IAddin*>(this));

		return hr;
	}

	STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom)
	{
		return S_OK;
	}

	// IRibbonExtensibility implementation
public:
	STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
	{
		if(!RibbonXml)
			return E_POINTER;
		*RibbonXml = GetXMLResource(IDR_XML1);
		return S_OK;
	}

	// ICustomTaskPaneConsumer Methods
	STDMETHOD(CTPFactoryAvailable)(ICTPFactory * pFactory) {
		// If the pane is already created, we're done
		if(pTaskPane)
			return S_OK;
		HRESULT hr;
		hr = pFactory->CreateCTP(CComBSTR("StatCloud.TaskPane"), CComBSTR("Chart Analysis"), vtMissing, &pTaskPane);
		
		CComPtr<IDispatch> pControlDisp;
		CComPtr<ITaskPaneControl> pControl;
		if(SUCCEEDED(hr))
			hr = pTaskPane->get_ContentControl(&pControlDisp);
		if(SUCCEEDED(hr))
			hr = pControlDisp->QueryInterface(&pControl);
		if(SUCCEEDED(hr))
			hr = pControl->Init(static_cast<IAddin*>(this));
		if(SUCCEEDED(hr))
			hr = pTaskPane->put_Visible(VARIANT_TRUE);
			
		return hr;
	}

	// IRibbonCallback
public:
	STDMETHOD(OnRibbonLoad)(IUnknown* pRibbonUIUnk) {
		if(!pRibbonUIUnk)
			return E_POINTER;

		return pRibbonUIUnk->QueryInterface(&pRibbonUI);
	}

public:
	// IAddin implementation
	// These are methods and properties that are available to the JavaScript runtime

	STDMETHOD(get_TaskPane)(IDispatch **ppPane) {
		*ppPane = NULL;
		if(pTaskPane) {
			return pTaskPane->QueryInterface(ppPane);
		} else {
			return E_FAIL;
		}
	}

	// Mostly to expose functionality for testing
	STDMETHOD(get_TaskPaneControl)(ITaskPaneControl ** ppCtrl) {
		HRESULT hr;
		if(pTaskPane == NULL) {
			*ppCtrl = NULL;
			return S_FALSE;
		} else {
			CComPtr<IDispatch> pDispatch;
			hr = pTaskPane->get_ContentControl(&pDispatch);
			if(SUCCEEDED(hr))
				hr = pDispatch->QueryInterface(ppCtrl);
			return hr;
		}
	} 

	STDMETHOD(get_Application)(IDispatch **ppApp) {
		return pApplication->QueryInterface(ppApp);
	}

	STDMETHOD(get_RibbonUI)(IDispatch **ppRibbon) {
		*ppRibbon = NULL;
		if(pRibbonUI)
			return pRibbonUI->QueryInterface(ppRibbon);
		else
			return S_FALSE;
	}

	STDMETHOD(InvalidateRibbon)() {
		HRESULT hr;
		if(pRibbonUI) {
			hr = pRibbonUI->Invalidate();
		}
		return S_OK;
	}

	STDMETHOD(InvalidateRibbonControl)(BSTR id) {
		HRESULT hr;
		if(!pRibbonUI)
			return S_OK;
		
		return pRibbonUI->InvalidateControl(id);
	}

	STDMETHOD(AddAppEventListener)(IDispatch *pListener, DWORD *pCookie) {
		HRESULT hr;

		CComQIPtr< IConnectionPointContainer > pContainer(pApplication);
		ATLASSERT( pContainer );

		CComPtr< IConnectionPoint > pConnectionPoint;

		// locate the PowerPoint Application-level ConnectionPoint
		hr = pContainer->FindConnectionPoint( __uuidof(PowerPoint::EApplication), &pConnectionPoint );
		ATLASSERT( hr == S_OK );

		CComObject<CPowerPointSink> * pWrapper;
		CComObject<CPowerPointSink>::CreateInstance(&pWrapper);
		pWrapper->SetJsObject(pListener);
		
		appEventsListeners.Add(pListener);

		return pConnectionPoint->Advise(static_cast<IDispatch*>(pWrapper), pCookie);
	}

	STDMETHOD(SetRibbonCallbackHandler)(IDispatch *pHandler) {
		this->pRibbonHandler = pHandler;	
		return S_OK;
	}

	STDMETHOD(BroadcastMessage)(BSTR message) {
		HRESULT hr;
		for(int i=0; i!=appEventsListeners.GetCount(); ++i) {
			CComDispatchDriver pDriver;
			if(SUCCEEDED(appEventsListeners.GetAt(i).CopyTo(&pDriver))) {
				CComVariant retVal;
				CComVariant varMessage(message);
				hr = pDriver.Invoke1(L"MessageBroadcast", &varMessage, &retVal);
			}
		}
		return S_OK;
	}

	HRESULT FindWindow(BSTR name, IHtmlWindow **ppWin) {
		for(int i=0; i!=windows.GetCount();++i) {
			CComBSTR windowName;
			windows.GetAt(i)->get_Name(&windowName);
			if(windowName == name) {
				return windows.GetAt(i).CopyTo(ppWin);
			}
		}
		return S_FALSE;
	}

	STDMETHOD(ShowWindow)(BSTR name, BSTR bookmark, BSTR title, int width, int height, IHtmlWindow **ppWindow) {
		CComObject<CHtmlWindow> *pWindow = NULL;
		
		HRESULT hr = FindWindow(name, ppWindow);

		if(hr == S_FALSE) {
			CComObject<CHtmlWindow>::CreateInstance(&pWindow);

			long hParentWnd;
			hr = pApplication->get_HWND(&hParentWnd);
			if(FAILED(hr))
				return hr;

			hr = pWindow->Init(static_cast<IAddin*>(this), (HWND)hParentWnd, 
								name, bookmark, title, width, height);
			if(SUCCEEDED(hr))
				hr = pWindow->QueryInterface(ppWindow);

			windows.Add(*ppWindow);

			return hr;
		} else {
			pWindow = static_cast<CComObject<CHtmlWindow> *>(*ppWindow);
			pWindow->put_Visible(VARIANT_TRUE);
			return pWindow->Navigate(bookmark);
		}
	}

	STDMETHOD(MessageBox)(BSTR text, int type, int *pResult) { 
		long hParentWnd;
		HRESULT hr = pApplication->get_HWND(&hParentWnd);
		if(FAILED(hr))
			return hr;
		
		*pResult = ::MessageBox((HWND)hParentWnd, text, L"StatCloud9", type);
		return S_OK;
	}

	STDMETHOD(Log)(BSTR message) {
		ATLTRACE2( message );
		return S_OK;
	}

	// We cannot can create COM SafeArrays in JavaScript
	// which need to in order to pass our chart data efficiently across process boundaries
	// to Excel.
	// This function creates a safe array from the given data accessor and calls put_Value2 on 
	// an excel range
	STDMETHOD(UpdateExcelRange)(IDispatch *pRangeDisp, IDispatch *pAccessor, int nRows, int nCols) {
		CComDispatchDriver accessor(pAccessor);

		// get the DISPID of the function that is going to give us our values
		DISPID getFunctionId;
		HRESULT hr = accessor.GetIDOfName(L"get", &getFunctionId);
		if(FAILED(hr))
			return E_INVALIDARG;

		// define our bounds
		SAFEARRAYBOUND bounds[2];  
		bounds[0].lLbound = 0;
		bounds[0].cElements = nRows;
		bounds[1].lLbound = 0;
		bounds[1].cElements = nCols;

		// Allocate the memory for our variant
		SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 2, bounds);

		// Fill the Array
		long ndx[2];  
		CComVariant rowIndex(0);
		CComVariant colIndex(0);

		VARIANT value;

		VARIANT emptyValue;
		VariantInit(&emptyValue);

		for ( int i = 0; i < nRows; i++ ) {       
			rowIndex.intVal = i;
			ndx[0] = i;

			for( int j = 0; j < nCols; ++j) {
				colIndex.intVal = j;
				ndx[1] = j;

				hr = accessor.Invoke2(getFunctionId, &rowIndex, &colIndex, &value);
				if(FAILED(hr)) {
					SafeArrayDestroy(psa);
					return hr;
				}
				if(V_VT(&value) == VT_BSTR && SysStringLen(V_BSTR(&value)) == 0) {
					// treat blank strings as VT_EMPTY
					hr = SafeArrayPutElement(psa, ndx, &emptyValue);
				} else {
					hr = SafeArrayPutElement(psa, ndx, &value);					
				}

				if(FAILED(hr)) {
					SafeArrayDestroy(psa);
					return hr;
				}
			}
		}

		VARIANT varpsa;

		V_VT(&varpsa) = VT_ARRAY | VT_VARIANT;
		V_ARRAY(&varpsa) = psa;

		CComPtr<Excel::Range> pRange;
		hr = pRangeDisp->QueryInterface(&pRange);
		if(SUCCEEDED(hr))
			hr = pRange->put_Value2(varpsa);
		
		SafeArrayDestroy(psa);

		return hr;
	}

	STDMETHOD(ExcelWorkbookForChartData)(IDispatch *pChartDataDisp, IDispatch **ppBook) {
		HRESULT hr;
		CComPtr<PowerPoint::ChartData> pChartData;

		hr = pChartDataDisp->QueryInterface(&pChartData);
		if(FAILED(hr))
			return E_INVALIDARG;

		CComPtr<IDispatch> pBookDisp;
		CComPtr<Excel::_Workbook> pBook;
		hr = pChartData->get_Workbook(ppBook);
		if(FAILED(hr)) {
			// the workbook may not be open, activate and try again
			hr = pChartData->Activate();
			if(FAILED(hr))
				return hr; // bail.

			hr = pChartData->get_Workbook(ppBook);
		}
		return hr;
	}
public:
		
	STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid)
	{
		static const IID* const arr[] = 
		{
			&IID_IAddin
		};

		for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
		{
			if (InlineIsEqualGUID(*arr[i],riid))
				return S_OK;
		}
		return S_FALSE;
	}


private: 
	HRESULT HrGetResource(int nId, 
		LPCTSTR lpType, 
		LPVOID* ppvResourceData,       
		DWORD* pdwSizeInBytes)
	{
		HMODULE hModule = _AtlBaseModule.GetModuleInstance();
		if (!hModule)
			return E_UNEXPECTED;
		HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType);
		if (!hRsrc)
			return HRESULT_FROM_WIN32(GetLastError());
		HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
		if (!hGlobal)
			return HRESULT_FROM_WIN32(GetLastError());
		*pdwSizeInBytes = SizeofResource(hModule, hRsrc);
		*ppvResourceData = LockResource(hGlobal);
		return S_OK;
	}

	BSTR GetXMLResource(int nId)
	{
		LPVOID pResourceData = NULL;
		DWORD dwSizeInBytes = 0;
		HRESULT hr = HrGetResource(nId, TEXT("XML"), 
			&pResourceData, &dwSizeInBytes);
		if (FAILED(hr))
			return NULL;
		// Assumes that the data is not stored in Unicode.
		CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData));
		return cbstr.Detach();
	}

	SAFEARRAY* GetOFSResource(int nId)
	{
		LPVOID pResourceData = NULL;
		DWORD dwSizeInBytes = 0;
		if (FAILED(HrGetResource(nId, TEXT("OFS"), 
			&pResourceData, &dwSizeInBytes)))
			return NULL;
		SAFEARRAY* psa;
		SAFEARRAYBOUND dim = {dwSizeInBytes, 0};
		psa = SafeArrayCreate(VT_UI1, 1, &dim);
		if (psa == NULL)
			return NULL;
		BYTE* pSafeArrayData;
		SafeArrayAccessData(psa, (void**)&pSafeArrayData);
		memcpy((void*)pSafeArrayData, pResourceData, dwSizeInBytes);
		SafeArrayUnaccessData(psa);
		return psa;
	}



};

OBJECT_ENTRY_AUTO(CLSID_StatCloudAddin, CAddin)
